fix: verify block and attestation signatures before processing#113
fix: verify block and attestation signatures before processing#113MegaRedHand wants to merge 3 commits intomainfrom
Conversation
🤖 Kimi Code ReviewSecurity & Correctness Issues1. Missing signature verification in
Fix: Either:
2. Race condition in BlockChainServer (lib.rs:297-305)
Fix: Consider acquiring a lock on the store during both pre-check and processing, or move signature verification into Performance & Design Issues3. Double state lookup (store.rs:165-170)
Fix: Consider passing the parent state to Code Quality4. Test modification creates inconsistency (signature_spectests.rs:55-65)
5. Error handling inconsistency
Recommendations
Automated review by Kimi (Moonshot AI) · custom prompt |
🤖 Codex Code ReviewFindings
If you want, I can suggest a small refactor to make signature verification mandatory and reuse the parent state lookup. Automated review by OpenAI Codex · custom prompt |
🤖 Claude Code ReviewNow I have enough context to provide a comprehensive review. Let me compile my findings: PR #113 Code Review: Verify block and attestation signatures before processingSummaryThis PR adds signature pre-checking for blocks received from the network before processing them. The main changes move signature verification earlier in the pipeline to fail fast on invalid signatures. Critical Issues1. Redundant State Retrieval in
|
Greptile OverviewGreptile SummaryThis PR factors block signature verification into a new The main integration points are:
Confidence Score: 2/5
|
| Filename | Overview |
|---|---|
| crates/blockchain/src/lib.rs | Adds an external-block signature precheck before processing in BlockChainServer::on_block(); however signature verification is now inconsistent across call paths because local blocks still bypass the precheck and store::on_block() no longer verifies signatures. |
| crates/blockchain/src/store.rs | Introduces precheck_block_signatures() and removes signature verification from on_block(); this makes on_block() accept blocks without cryptographic verification unless every caller remembers to precheck first. |
| crates/blockchain/tests/signature_spectests.rs | Updates signature spec tests to call precheck_block_signatures() before on_block(); test harness still reflects the new split but can mask missing verification in other call paths. |
Sequence Diagram
sequenceDiagram
participant P2P as P2P Layer
participant BC as BlockChainServer (lib.rs)
participant ST as store.rs
participant FS as Forkchoice Store
P2P->>BC: NewBlock(SignedBlockWithAttestation)
BC->>BC: check parent exists
alt parent missing
BC->>P2P: FetchBlock(parent_root)
BC->>BC: queue as pending
else parent exists
BC->>ST: precheck_block_signatures(&Store, &signed_block)
alt signatures invalid
ST-->>BC: Err(StoreError)
BC-->>P2P: (drop block)
else signatures ok
ST-->>BC: Ok(())
BC->>ST: on_block(&mut Store, signed_block)
ST->>FS: state_transition + insert block/state
ST->>FS: update_head + process attestations
ST-->>BC: Ok(())
end
end
Note over BC,ST: Local proposal path currently calls store::on_block() via process_block() without precheck
Additional Comments (2)
Fix: either reintroduce the signature check inside Also appears in: Prompt To Fix With AIThis is a comment left during a code review.
Path: crates/blockchain/src/store.rs
Line: 337:379
Comment:
**Signature verification skipped**
`on_block()` no longer verifies signatures (verification was moved to `precheck_block_signatures()`), but `on_block()` is still callable directly (e.g. from tests and any other internal callers) and now accepts blocks without any cryptographic checks. This is a real behavior change: any caller that forgets to invoke `precheck_block_signatures()` first will import invalid blocks into the store.
Fix: either reintroduce the signature check inside `on_block()` (and optionally keep `precheck_block_signatures()` as an early fast-fail), or make `on_block()` private and expose a single public API that always performs prechecks before state transition.
Also appears in: `crates/blockchain/tests/signature_spectests.rs:63-64` (calls `on_block()` after an optional precheck).
How can I resolve this? If you propose a fix, please make it concise.
This new precheck is only applied in the external Prompt To Fix With AIThis is a comment left during a code review.
Path: crates/blockchain/src/lib.rs
Line: 286:304
Comment:
**Inconsistent signature enforcement**
This new precheck is only applied in the external `BlockChainServer::on_block()` path, but locally-built blocks go straight through `process_block()` (which calls `store::on_block()`) without any signature verification. If signature verification is meant to be an invariant, it should live in `store::on_block()` (or `process_block()`), not just in one call-site; otherwise local vs external blocks are treated differently and `store::on_block()` remains unsafe for other callers.
How can I resolve this? If you propose a fix, please make it concise. |
…fied variants (#157) ## Motivation PR #113 proposed extracting signature verification out of `on_block()` into a separate `precheck_block_signatures()` function called by the network layer before processing. Four automated reviewers flagged a critical security flaw: removing `verify_signatures()` from `on_block()` means any caller that forgets to call `precheck_block_signatures()` first can import blocks without cryptographic validation. There are 3 call sites for `on_block()` (production `process_block()`, and 2 test files), and only 1 would get the precheck. PR #154 responded by taking the conservative approach: keep `on_block()` unchanged and document its validate-then-mutate structure. It argued against extraction. This PR is the compromise: instead of removing verification from `on_block()`, we **keep `on_block()` as the safe default** (always verifies) and add `on_block_without_verification()` as an explicit opt-out. This follows Zeam's architecture where `ForkChoice.onBlock()` is signature-agnostic and verification happens at the chain layer, but inverted — verification is the default and opting out requires calling a function whose name makes the risk obvious. This eliminates the `skip-signature-verification` compile-time feature flag entirely. ## Description ### `store.rs` — Split `on_block()` Three functions replace the old `on_block()`: | Function | Visibility | Verifies? | Stores sigs? | Used by | |----------|-----------|-----------|-------------|---------| | `on_block()` | public | Yes | Yes | Production (`lib.rs`), `signature_spectests` | | `on_block_without_verification()` | public | No | No | `forkchoice_spectests` | | `on_block_core()` | private | — | — | Shared logic for both | `on_block_core()` takes `parent_state` by value (avoids the previous `.clone()` — minor perf win). The 4-line dedup + parent-state preamble is duplicated in both public functions to avoid double deserialization (verification needs the state, core needs the state). ### `store.rs` — Split `on_gossip_attestation()` Same pattern: | Function | Visibility | Verifies? | Stores sigs? | |----------|-----------|-----------|-------------| | `on_gossip_attestation()` | public | Yes | Yes | | `on_gossip_attestation_without_verification()` | public | No | No | Both share the validation + `on_attestation()` + logging logic. Some duplication is acceptable to keep each function self-contained. ### `Cargo.toml` — Remove feature flag - Removed `[features]` section with `skip-signature-verification` - Removed `required-features = ["skip-signature-verification"]` from forkchoice test entry ### `forkchoice_spectests.rs` - Changed `store::on_block()` → `store::on_block_without_verification()` - Fixed 4 pre-existing `collapsible_if` clippy warnings (now surfaced because the test no longer requires a feature flag to compile, so `clippy --all-targets` sees it) ### `Makefile` - Removed `--features skip-signature-verification` from `test` target ### `CLAUDE.md` and `RELEASE.md` - Removed all `skip-signature-verification` references from test commands, gotchas, and Docker build examples ## Relationship to #113 and #78 This PR supersedes #113's approach. Rather than extracting verification to a separate precheck (which creates the "forgotten precheck" security risk flagged by reviewers), it splits the function into safe and unsafe variants. The safe variant (`on_block()`) always verifies — callers can't accidentally skip verification. The unsafe variant (`on_block_without_verification()`) makes the risk explicit in its name. Issue #78's concern about signature verification is fully addressed: production paths always verify, and the compile-time feature flag is eliminated. ## How to test ```bash make fmt make lint make test cargo test -p ethlambda-blockchain --test forkchoice_spectests cargo test -p ethlambda-blockchain --test signature_spectests --release ``` Confirm no feature flag remnants: ```bash grep -r "skip-signature-verification" crates/ ``` Note: `signature_spectests` have pre-existing failures (fixture deserialization issue with `ProposerSignature`). These failures are identical on `main` and are unrelated to this change. --------- Co-authored-by: Tomás Grüner <47506558+MegaRedHand@users.noreply.github.com>
Closes #78
NOTE: this PR's changes are unreviewed